
/* GCSx
** EDITBOX.H
**
** Multi-line textbox window
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#ifndef __GCSx_EDITBOX_H_
#define __GCSx_EDITBOX_H_

// d) Data line- one line in storage
// v) Virtual line- one line displayed
// One data line results in one OR MORE virtual lines

class EditBox : public Window {
public:
    // Definition of a point within textbox- points to the character it is before
    struct TextPoint {
        int vRow;
        int column;
        int pixelX;
    };
    
protected:
    // Maps virtual lines to data
    struct VLine {
        int dLine;
        std::list<std::string>::iterator dLineI;
        int colStart; // first row will have 0
        int length; // length of 5 means chars 0-4 plus a blank spot at the end
        const std::string getStr() const { return (*dLineI).substr(colStart, length); }
    };
    std::list<VLine> lineMap;
    void fillLineMap();

    // Returns number of vlines added
    // doesn't update vNumLines or dNumLines; does update vLongestLength
    int addLineWrapped(int dLine, std::list<std::string>::iterator& iter, std::list<VLine>::iterator& insertAt);
    int breakLineBefore(const std::string& line, int xPixel);

    // Assumes contents, starting at given line, have been deleted or added already
    // Call in sequence for a modification of x lines
    // Doesn't adjust insertion/selection, resize, or dirty
    // (insert adjusts vLongestLength)
    void eraseLineMap(int dLineFirst, int count, int* getVLineAt = NULL, int* getVLineCount = NULL);
    void insertLineMap(int dLineAt, int count, int* getVLineAt = NULL, int* getVLineCount = NULL);
    void dumpLineMap();
    
    // Adjusts cursor/selection and resizes
    void contentModifyHelper(int vLine, int vCount);
    
    // Call if you modify within a single line only- does everything except update insertion/selection
    // and doesn't call pre/post modify
    // Assumes you've called vLineToDLineAllPoints and that points are in dlines right now
    // (update linemap, resize, dirty)
    // All changes that don't go through pasteText() go through this function
    void modifyDLine(int dLine);
    
    // Helper function
    void doIndent(int dLine, int count, int function);
    
    int open;
    int haveFocus;
    
    class FrameWindow* myFrame;
    class WidgetScroll* myScroll;
    
    int font;
    int readonly;
    int wordwrap;
    int wrappedTo;
    int autoIndent;
    
    // Dirty range, in Vlines
    int vDirtyFirst;
    int vDirtyLast;

    // A copy of storage for convenience
    std::list<std::string>* contents;
    int dNumLines;
    int vNumLines;
    int vLongestLength; // In pixels
    int lineHeight;
    
    // Iterator control for quickly finding a spot in the middle
    std::list<VLine>::iterator lastSpot;
    int lastSpotLine;
    std::list<VLine>::iterator findVLine(int line);
    // Finds FIRST of a given data line
    std::list<VLine>::iterator findDLine(int dLine, int* getVLineNum = NULL);
    
    // Insertion point and selection
    // Selection begin OR end will ALWAYS match insertpoint UNLESS
    // there is no selection, in which case both are indeterminate
    TextPoint insertion;
    TextPoint selectBegin;
    TextPoint selectEnd;
    int isSelect;
    
    // Insertion point
    int insertYOffset;
    int insertHeight;
    int insertBlink;
    Uint32 insertBlinkMs;
    
    // Characters that make up "words"
    static const std::string wordChars;

    enum {
        GUI_EDITBOX_INSERTPOINTWIDTH = 2,
        // Margin is blank space inside textbox border
        GUI_EDITBOX_INNERMARGIN = 0,
        // Padding is added to edges of text within textbox, but only shows when
        // fully scrolled to that end of the text
        GUI_EDITBOX_LEFTPAD = 3,
        GUI_EDITBOX_RIGHTPAD = 3,
        // Preferred number of pixels to show in excess on each side when scrolling
        GUI_EDITBOX_SCROLLAHEAD = 10,

        // MS to wait between blinks
        DELAY_CURSOR_BLINK = 500,

        // These only apply when used as a widget!
        // This is the number of X characters to fit in a standard width
        GUI_EDITBOX_DEFAULTWIDTH = 30,
        // Default number of lines to show
        GUI_EDITBOX_DEFAULTLINES = 20,
        
        // Tab size (replaced with spaces)
        TAB_SIZE = 3,
    };
    
    // Sets dirty and resets cursor blink
    void setDirtyAndBlink();
    
    // Sets range of lines dirty
    void setDirtyRange(int first, int last);
    
    // Move one character left or right; pixelX will not be accurate
    void goLeft(TextPoint& point);
    void goRight(TextPoint& point);
    
    // Convert a textpoint from vline to dline position and back
    // A dline textpoint is useless until converted back, but can be
    // used to help retain relative position when doing modifications
    // Converting to VLine also updates xpixel.
    void vLineToDLine(TextPoint& point);
    void dLineToVLine(TextPoint& point);
    void vLineToDLineAllPoints();
    void dLineToVLineAllPoints();
    
    // Called whenever content changes, BEFORE and AFTER it changes
    // Will consist of any number of operations (in any order) ending in DONE
    // Reminder: line numbers are 0-based
    enum ContentChangeType {
        EDITBOX_REMOVE_LINES = 1,
        EDITBOX_INSERT_LINES, // It's assumed these are modified as well
        EDITBOX_MODIFY_LINE, // Only one at a time
        EDITBOX_MODIFY_DONE, // Called after each batch (even if just one) of modifications
    };
    virtual void contentModifiedPre(ContentChangeType type, int firstRow, int numRows);
    virtual void contentModifiedPost(ContentChangeType type, int firstRow, int numRows);
    
    // Update titlebar of frame
    virtual void updateTitlebar();

    virtual void prepOpen();
    virtual void paintText(const std::string& text, const std::string& entireLine, int posInLine, int x, int y, SDL_Surface* destSurface, int isSelected = 0) const;
    
public:
    // Edited directly in storage
    EditBox(std::list<std::string>* storage, int myFont = FONT_STANDARD, int isReadOnly = 0);
    virtual ~EditBox();
    
    virtual int event(int hasFocus, const SDL_Event* event);
    void load();
    void apply();
    void display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset);
    virtual CommandSupport supportsCommand(int code) const;
    WindowType windowType() const;

    // Creates a FrameWindow, adds self to desktop
    class FrameWindow* runWindowed();
    
    // Adds self to dialog using a WScroll
    void addTo(class Dialog* dialog, int showWidth = -1, int showLines = -1);

    // Returns the insertion position, given a pixel X/Y offset; X/Y offset
    // does not need to be within the actual display boundaries (IE you can drag
    // past the edges) .pixelX member is not accurate
    TextPoint insertWhere(int x, int y);
    // Same, but given a row instead of y pixel
    TextPoint insertWhereRow(int x, int row);
    
    // Handle special keystrokes
    void keyBackspace();
    void keyDelete();
    void indent(int dLine, int count);
    void unindent(int dLine, int count);
    
    // Query or set the insertion point, which also clears selection and
    // scrolls if needed to view; if drag is true, this drags the current
    // insertion point to the given position, modifying the selection as well
    const TextPoint& insertionState() const;
    // .pixelX member does not need to be accurate
    // .pixelX member is modified to be accurate in original and return
    // no other modifications are done to newPos
    const TextPoint& insertionState(TextPoint& newPos, int drag = 0);
    
    // (sel = ins if no selection at this time)
    void getCursorData(TextPoint& ins, TextPoint& selBegin, TextPoint& selEnd) const;
    // Intended to be called during undo process, but should be safe regardless
    // May change .pixelX members- but not gauranteed
    void setCursorData(TextPoint& ins, TextPoint& selBegin, TextPoint& selEnd);

    // Selects entire text, scrolls to end
    void selectAll();
    
    // Inserts text at the insertion point, overwriting any selection
    // No text = clear selection
    // Newlines are interpreted
    // Resets selection and adjusts insertion and scroll points
    // MOST changes go through this function; see modifyDLine
    void pasteText(const std::string& text);
    
    // Alerts editbox that its contents have changed in some way
    // Automatically handles dirty, scroll, moving insertion point, etc as needed
    // (line numbers 0-based)
    // Lines are DATA lines
    void contentModify(int dLine, int countLines);
    void contentInsert(int dLine, int countLines);
    void contentDelete(int dLine, int countLines);
    
    // Copies current selection to buffer
    // Copies "" if no selection
    // Newlines are inserted between lines
    void copyText(std::string& buffer);
    
    // Are we open?
    int isOpen() const { return open; }
    
    void resize(int newWidth, int newHeight, int newViewWidth = -1, int newViewHeight = -1, int fromParent = 0);
};

#endif

